/*
 * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.xuexiang.xdlink.component.channel.impl.socket.client;

import android.os.Looper;

import com.xuexiang.xdlink.component.channel.impl.AbstractChannel;
import com.xuexiang.xdlink.exception.ChannelException;
import com.xuexiang.xdlink.logs.DCFLog;
import com.xuexiang.xdlink.util.ConvertTools;
import com.xuexiang.xdlink.util.DCFExecutors;
import com.xuexiang.xdlink.util.Utils;

import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.Callable;

/**
 * 抽象的客户端socket 通信通道
 *
 * @author xuexiang
 * @since 2018/9/5 下午2:31
 */
public abstract class AbstractClientSocketChannel extends AbstractChannel {
    /**
     * 默认连接超时时间（10s）
     */
    static final int DEFAULT_CONNECT_TIMEOUT = 10 * 1000;
    /**
     * 默认缓冲大小
     */
    static final int DEFAULT_BUFFER_SIZE = 3000;

    /**
     * 需要连接的服务端IP地址
     */
    String mIP;
    /**
     * 开放的端口号
     */
    int mPort;
    /**
     * 连接的超时时间
     */
    int mTimeout;
    /**
     * 缓存大小
     */
    private int mBufferSize;
    /**
     * 是否自动切换线程
     */
    private boolean mIsAutoSwitchThread;

    /**
     * socket连接
     */
    Socket mSocket;

    /**
     * 构造方法
     *
     * @param ip                 需要连接的服务端IP地址
     * @param port               开放的端口号
     * @param timeout            连接的超时时间
     * @param bufferSize         缓存大小
     * @param isAutoSwitchThread 是否自动切换线程
     */
    public AbstractClientSocketChannel(String ip, int port, int timeout, int bufferSize, boolean isAutoSwitchThread) {
        mIP = ip;
        mPort = port;
        mTimeout = timeout;
        mBufferSize = bufferSize;
        mIsAutoSwitchThread = isAutoSwitchThread;
    }

    @Override
    public void openChannel() {
        if (isOpen()) return;

        DCFExecutors.get().addTask(new Runnable() {
            @Override
            public void run() {
                connectSocket();
            }
        });
    }

    /**
     * 建立socket连接
     */
    protected abstract void connectSocket();

    /**
     * 构建socket连接
     *
     * @return
     * @throws Exception
     */
    protected Socket createSocket() throws Exception {
        Socket socket = new Socket();
        socket.setTcpNoDelay(true);
        socket.setSoLinger(true, 0);
        socket.setKeepAlive(true); // 开启保持活动状态的套接字
        socket.setSendBufferSize(mBufferSize); //设置发送数据的缓冲大小
        socket.setReceiveBufferSize(mBufferSize); //设置接收数据的缓冲大小
        return socket;
    }

    @Override
    public boolean isOpen() {
        return super.isOpen() && mSocket != null && !mSocket.isClosed() && mSocket.isConnected();
    }

    @Override
    public void closeChannel() {
        close();
    }

    @Override
    protected void close() {
        stopAcceptData();
        updateOpenedStatus(false);

        Utils.closeIO(mSocket);
        mSocket = null;
    }

    @Override
    public void write(final byte[] message) throws ChannelException {
        if (isMainThread()) {
            if (mIsAutoSwitchThread) {
                DCFExecutors.get().addTask(new Callable<Boolean>() {
                    @Override
                    public Boolean call() throws Exception {
                        writeBytes(message);
                        return true;
                    }
                });
            } else {
                throw new ChannelException(ChannelException.WRITE_ON_MAIN_THREAD_ERROR, "Socket[client] write error in main thread：" + ConvertTools.bytes2HexString(message));
            }
        } else {
            writeBytes(message);
        }
    }

    private boolean isMainThread() {
        return Looper.getMainLooper() == Looper.myLooper();
    }

    /**
     * 写bytes
     *
     * @param message 消息
     * @throws ChannelException
     */
    protected void writeBytes(byte[] message) throws ChannelException {
        try {
            if (mSocket != null) {
                OutputStream os = mSocket.getOutputStream();
                os.write(message);
                os.flush();
                DCFLog.d("Socket[client] data sent successfully，data length：" + message.length);
            }
        } catch (Exception e) {
            throw new ChannelException(ChannelException.WRITE_FAILED_ERROR, e);
        }
    }


}
